<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue2.X响应式原理</title>
</head>
<body>
  <div id="app"></div>
  <script>
    // 1.创建一个构造函数
    function Vue(options) {
      // 2.将options保存到vm上
      this.$options = options;
      // 3.将data保存到vm和data中
      var data = this._data = this.$options.data;
      // 4.将vm代理data
      proxy(this, '_data');
      // 5.调用observe方法，监听数据的变化
      observe(data);
      // 6.创建一个编译对象
      new Compile(options.el, this);
    }

    // 代理函数
    function proxy(vm, sourceKey) {
      // 遍历vm[sourceKey]中的所有属性
      Object.keys(vm[sourceKey]).forEach(key => {
        // 将vm[sourceKey]中的属性代理到vm上
        Object.defineProperty(vm, key, {
          enumerable: true,
          configurable: true,
          get() {
            return vm[sourceKey][key];
          },
          set(newValue) {
            vm[sourceKey][key] = newValue;
          }
        })
      })
    }

    // 观察者
    function Observe(data) {
      // 保存data
      this.data = data;
      // 开始监视data中的所有数据
      this.walk(data);
    }

    Observe.prototype = {
      walk(data) {
        // 保存observe对象
        var me = this;
        // 遍历data中所有属性
        Object.keys(data).forEach(key => {
          // 对data中的每个属性进行监视
          me.defineReactive(data, key, data[key]);
        })
      },
      defineReactive(data, key, value) {
        // 递归调用，监视data中的所有属性
        observe(value);
        // 为data中的每个属性创建一个Dep对象
        var dep = new Dep();
        // 给data中的每个属性重新定义属性
        Object.defineProperty(data, key, {
          enumerable: true,
          configurable: true,
          get() {
            // 判断Dep.target是否有值
            Dep.target && dep.addSub(Dep.target);
            return value;
          },
          set(newValue) {
            if (newValue === value) {
              return;
            }
            // 将最新的
            value = newValue;
            // 通知所有订阅者
            dep.notify();
          }
        })
      }
    }
  </script>
</body>
</html>